import mountElement from './mountElement';
import updateTextNode from './updateTextNode';
import updateNodeElement from './updateNodeElement';
import createDomElement from './createDomElement';
import unmountNode from './unmountNode';
import diffComponent from './diffComponent';

export default function diff(vDom, container, oldDom) {
  const oldVDom = oldDom && oldDom._vDom;
  const oldComponent = oldVDom && oldVDom.component; // 获取到旧组件的实例对象
  // 1、判断oldDom是否存在
  if (!oldDom) { // 如果没有oldDom 直接生成新的真实DOM
    mountElement(vDom, container);
  }
  // 如果有 oldDom 并且新旧节点的 type 不一样 且不是一个组件
  else if (oldVDom && oldVDom.type !== vDom.type && typeof vDom.type !== 'function') {
    const newElement = createDomElement(vDom);
    oldDom.parentNode.replaceChild(newElement, oldDom);
  }
  // 如果新节点是一个组件 
  else if (typeof vDom.type === 'function') {
    diffComponent(vDom, oldComponent, oldDom, container);
  }
  // 如果新旧节点的 type 相同
  else if (oldVDom && oldVDom.type === vDom.type) {
    if (vDom.type === 'text') { // 文本类型的节点 更新文本内容
      updateTextNode(vDom, oldVDom, oldDom);
    } else { // 如果是元素节点 更新元素属性
      updateNodeElement(oldDom, vDom, oldVDom);
    }

    // 1、将拥有 key 属性的子元素 放置在一个单独的对象中
    let keyedElements = {};
    for (let i = 0, len = oldDom.childNodes.length; i < len; i++) {
      let domElement = oldDom.childNodes[i];
      if (domElement.nodeType === 1) { // 如果 nodeType 是 1 说明是元素节点不是文本节点
        let key = domElement.getAttribute('key');
        if (key) {
          keyedElements[key] = domElement;
        }
      }
    }

    let noKey = Object.keys(keyedElements).length === 0;
    if (noKey) { // 如果没有带key的子节点 就用索引进行对比
      vDom.children.forEach((child, i) => { // 递归子节点对比差异
        diff(child, oldDom, oldDom.childNodes[i]);
      });
    } else { // 如果有带key的子节点
      // 2、循环 vDom 的子元素 获取子元素的 key 属性。然后去 keyedElements 找 如果找的到说明存在
      vDom.children.forEach((child, i) => {
        let key = child.props.key;
        if (key) {
          let domElement = keyedElements[key];
          if (domElement) { // 查找到了 说明已经存在
            // 3、看看当前位置的元素是不是我们期望的元素 如果不是就得调换位置
            if (oldDom.childNodes[i] && oldDom.childNodes[i] !== domElement) {
              // 如果不是 调换位置。把新的子节点插入到当前子节点之前，这样新的子节点就是当前子节点了
              oldDom.insertBefore(domElement, oldDom.childNodes[i]);
            }
          } else {
            // 新增的元素
            mountElement(child, oldDom, oldDom.childNodes[i], true);
          }
        }
      });
    }

    // 删除节点 当旧节点的子元素多余了新节点的子元素的长度 说明需要删除
    let oldChildNodes = oldDom.childNodes;
    if (oldChildNodes.length > vDom.children.length) {
      if (noKey) { // 没有带key的子元素 通过索引删除多余的元素
        for (let i = oldChildNodes.length - 1; i > vDom.children.length - 1; i--) {
          unmountNode(oldChildNodes[i]);
        }
      } else { // 有带 key 的子元素 要通过 key 去删除
        for (let i = 0; i < oldChildNodes.length; i++) {
          const oldChild = oldChildNodes[i]; // 获取旧的节点
          const oldChildKey = oldChild._vDom.props.key; // 获取旧节点的 key
          let found = false;
          for (let j = 0; j < vDom.children.length; j++) { // 用旧节点的 key 去新子元素里面找
            if (oldChildKey === vDom.children[j].props.key) {
              found = true;
              break;
            }
          }
          if (!found) { // 没找到就需要被删除
            unmountNode(oldChild);
          }
        }
      }
    }
  }
}
